﻿using System;
using System.Text;
using System.Net;
using System.Xml.Serialization;
using System.Threading;
using System.ComponentModel;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Collections;

namespace SimpleMediaManager.TMDB
{
    public class Api
    {
        private const string MOVIE_SEARCH_URL = "http://api.themoviedb.org/2.1/Movie.search/en/xml/{0}/{1}";
        private const string MOVIE_IMDB_LOOKUP_URL = "http://api.themoviedb.org/2.1/Movie.imdbLookup/en/xml/{0}/{1}";
        private const string MOVIE_GET_INFO_URL = "http://api.themoviedb.org/2.1/Movie.getInfo/en/xml/{0}/{1}";
        private const string PERSON_SEARCH_URL = "http://api.themoviedb.org/2.1/Person.search/en/xml/{0}/{1}";
        private const string PERSON_GET_INFO_URL = "http://api.themoviedb.org/2.1/Person.getInfo/en/xml/{0}/{1}";

        public delegate void MovieSearchAsyncCompletedEventHandler(object sender, MovieSearchCompletedEventArgs e);
        public delegate void MovieInfoAsyncCompletedEventHandler(object sender, MovieInfoCompletedEventArgs e);
        public delegate void PersonSearchAsyncCompletedEventHandler(object sender, PersonSearchCompletedEventArgs e);
        public delegate void PersonInfoAsyncCompletedEventHandler(object sender, PersonInfoCompletedEventArgs e);

        public event MovieSearchAsyncCompletedEventHandler MovieSearchCompleted;
        public event MovieSearchAsyncCompletedEventHandler MovieSearchByImdbCompleted;
        public event MovieInfoAsyncCompletedEventHandler GetMovieInfoCompleted;
        public event PersonSearchAsyncCompletedEventHandler PersonSearchCompleted;
        public event PersonInfoAsyncCompletedEventHandler GetPersonInfoCompleted;

        public string Key { get; set; }

        public Api(string key)
        {
            this.Key = key;
        }

        public Movie[] MovieSearch(string title)
        {
            MovieSearchResults search = CallApi<MovieSearchResults>(string.Format(MOVIE_SEARCH_URL, this.Key, Escape(title)));

            return search.Movies;
        }

        public void MovieSearchAsync(string title)
        {
            MovieSearchAsync(title, null);
        }

        public void MovieSearchAsync(string title, object userState)
        {
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
            MovieSearchDelegate worker = new MovieSearchDelegate(MovieSearchWorker);

            worker.BeginInvoke(title, userState, asyncOp, null, null);
        }

        private delegate void MovieSearchDelegate(string title, object userState, AsyncOperation asyncOp);
        private void MovieSearchWorker(string title, object userState, AsyncOperation asyncOp)
        {
            Exception exception = null;
            Movie[] movies = null;

            try
            {
                movies = MovieSearch(title);
            }
            catch (Exception ex)
            {
                exception = ex;
            }

            MovieSearchCompletedEventArgs args = new MovieSearchCompletedEventArgs(movies, exception, false, userState);

            asyncOp.PostOperationCompleted(delegate(object e) { OnMovieSearchCompleted((MovieSearchCompletedEventArgs)e); }, args);
        }

        protected virtual void OnMovieSearchCompleted(MovieSearchCompletedEventArgs e)
        {
            if (MovieSearchCompleted != null) MovieSearchCompleted(this, e);
        }

        public Movie[] MovieSearchByImdb(string imbdId)
        {
            MovieSearchResults search = CallApi<MovieSearchResults>(string.Format(MOVIE_IMDB_LOOKUP_URL, this.Key, imbdId));

            return search.Movies;
        }

        public void MovieSearchByImdbAsync(string imdbId)
        {
            MovieSearchByImdbAsync(imdbId, null);
        }

        public void MovieSearchByImdbAsync(string imdbId, object userState)
        {
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
            MovieSearchDelegate worker = new MovieSearchDelegate(MovieSearchByImdbWorker);
            worker.BeginInvoke(imdbId, userState, asyncOp, null, null);
        }

        private delegate void MovieSearchByImdbDelegate(string imdbId, object userState, AsyncOperation asyncOp);
        private void MovieSearchByImdbWorker(string imdbId, object userState, AsyncOperation asyncOp)
        {
            Exception exception = null;
            Movie[] movies = null;

            try
            {
                movies = MovieSearchByImdb(imdbId);
            }
            catch (Exception ex)
            {
                exception = ex;
            }

            MovieSearchCompletedEventArgs args = new MovieSearchCompletedEventArgs(movies, exception, false, userState);
            asyncOp.PostOperationCompleted(delegate(object e) { OnMovieSearchByImdbCompleted((MovieSearchCompletedEventArgs)e); }, args);
        }

        protected virtual void OnMovieSearchByImdbCompleted(MovieSearchCompletedEventArgs e)
        {
            if (MovieSearchByImdbCompleted != null) MovieSearchByImdbCompleted(this, e);
        }

        public Movie GetMovieInfo(int id)
        {
            MovieSearchResults search = CallApi<MovieSearchResults>(string.Format(MOVIE_GET_INFO_URL, this.Key, id));

            if (search.Movies.Length == 1)
            {
                return search.Movies[0];
            }
            else
            {
                return null;
            }
        }

        public void GetMovieInfoAsync(int id)
        {
            GetMovieInfoAsync(id, null);
        }

        public void GetMovieInfoAsync(int id, object userState)
        {
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
            GetMovieInfoDelegate worker = new GetMovieInfoDelegate(GetMovieInfoWorker);
            worker.BeginInvoke(id, userState, asyncOp, null, null);
        }

        private delegate void GetMovieInfoDelegate(int id, object userState, AsyncOperation asyncOp);
        private void GetMovieInfoWorker(int id, object userState, AsyncOperation asyncOp)
        {
            Exception exception = null;
            Movie movie = null;

            try
            {
                movie = GetMovieInfo(id);
            }
            catch (Exception ex)
            {
                exception = ex;
            }

            MovieInfoCompletedEventArgs args = new MovieInfoCompletedEventArgs(movie, exception, false, userState);
            asyncOp.PostOperationCompleted(delegate(object e) { OnGetMovieInfoCompleted((MovieInfoCompletedEventArgs)e); }, args);
        }

        protected virtual void OnGetMovieInfoCompleted(MovieInfoCompletedEventArgs e)
        {
            if (GetMovieInfoCompleted != null) GetMovieInfoCompleted(this, e);
        }

        public Person[] PersonSearch(string name)
        {
            PersonSearchResults search = CallApi<PersonSearchResults>(string.Format(PERSON_SEARCH_URL, this.Key, Escape(name)));

            return search.People;
        }

        public void PersonSearchAsync(string name)
        {
            PersonSearchAsync(name, null);
        }

        public void PersonSearchAsync(string name, object userState)
        {
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
            PersonSearchDelegate worker = new PersonSearchDelegate(PersonSearchWorker);
            worker.BeginInvoke(name, userState, asyncOp, null, null);
        }

        private delegate void PersonSearchDelegate(string name, object userState, AsyncOperation asyncOp);
        private void PersonSearchWorker(string name, object userState, AsyncOperation asyncOp)
        {
            Exception exception = null;
            Person[] people = null;

            try
            {
                people = PersonSearch(name);
            }
            catch (Exception ex)
            {
                exception = ex;
            }

            PersonSearchCompletedEventArgs args = new PersonSearchCompletedEventArgs(people, exception, false, userState);
            asyncOp.PostOperationCompleted(delegate(object e) { OnPersonSearchCompleted((PersonSearchCompletedEventArgs)e); }, args);
        }

        protected virtual void OnPersonSearchCompleted(PersonSearchCompletedEventArgs e)
        {
            if (PersonSearchCompleted != null) PersonSearchCompleted(this, e);
        }

        public Person GetPersonInfo(int id)
        {
            PersonSearchResults search = CallApi<PersonSearchResults>(string.Format(PERSON_SEARCH_URL, this.Key, id));

            if (search.People.Length == 1)
            {
                return search.People[0];
            }
            else
            {
                return null;
            }
        }

        public void GetPersonInfoAsync(int id)
        {
            GetPersonInfoAsync(id, null);
        }

        public void GetPersonInfoAsync(int id, object userState)
        {
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
            GetPersonInfoDelegate worker = new GetPersonInfoDelegate(GetPersonInfoWorker);
            worker.BeginInvoke(id, userState, asyncOp, null, null);
        }

        private delegate void GetPersonInfoDelegate(int id, object userState, AsyncOperation asyncOp);
        private void GetPersonInfoWorker(int id, object userState, AsyncOperation asyncOp)
        {
            Exception exception = null;
            Person person = null;

            try
            {
                person = GetPersonInfo(id);
            }
            catch (Exception ex)
            {
                exception = ex;
            }

            PersonInfoCompletedEventArgs args = new PersonInfoCompletedEventArgs(person, exception, false, userState);
            asyncOp.PostOperationCompleted(delegate(object e) { OnGetPersonInfoCompleted((PersonInfoCompletedEventArgs)e); }, args);
        }

        protected virtual void OnGetPersonInfoCompleted(PersonInfoCompletedEventArgs e)
        {
            if (GetPersonInfoCompleted != null) GetPersonInfoCompleted(this, e);
        }

        private T CallApi<T>(string url)
        {
            WebClient webClient = new WebClient();
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            byte[] data = webClient.DownloadData(url);
            TextReader xml = new StringReader(System.Text.Encoding.UTF8.GetString(data));
            T result = (T)serializer.Deserialize(xml);

            webClient.Dispose();

            return result;
        }

        private string Escape(string s)
        {
            return Regex.Replace(s, "[" + Regex.Escape(new String(Path.GetInvalidFileNameChars())) + "]", "-");
        }
    }
}
